19 枚举

枚举类型

使用枚举类相比静态变量的优势:
1.使用枚举作为参数,有类型检查的机制,比String作为参数更加安全。
2.提供了很多方法来操作枚举类,更加方便。
3.甚至可以自定义某个枚举类相关的方法。

values()的神秘之处

枚举类继承了Enum,但是Enum并没有values()方法,那么,values()从哪来的?

由编译器帮我们生成。至于为什么这样处理,暂不了解。

Enum没有实现values()方法带来的问题?

向上转型的枚举类无法使用values方法,也就意味着无法通过Enum来获取具体的类型信息。
当然,我们知道,使用反射基本是无所不能的,并且反射对枚举类型有一些特殊的处理方法。
通过getClass()可以获取实际的枚举类型,然后调用getEnumConstants()可以获取枚举类。

编译器还帮我们做了哪些处理?

  1. 继承了Enum类
  2. 加上final关键词,使得无法被继承。
  3. 重载了valueOf()方法。

    注意是继承,也就意味着枚举类无法继承其他的类。

分类

由于枚举无法继承,有时也会带来一些使用上的不便,比如给枚举分类的问题,比如拓展枚举类的问题。

对于无法继承的补偿:

  1. 使用实现进行分类
1
2
3
4
5
6
7
8
9
10

public interface Food {
enum Appetizer implements Food {SALAD, SOUP, SPRING_ROLLS;}

enum MainCourse implements Food {LASAGNE, BURRITO, PAD_THAI, LENTILS, HUMMOUS, VINDALOO;}

enum Dessert implements Food {TIRAMISU, GELATO, BLACK_FOREST_CAKE, FRUIT, CREME_CARAMEL;}

enum Coffee implements Food {BLACK_COFFEE, DECAF_COFFEE, ESPRESSO, LATTE, CAPPUCCINO, TEA, HERB_TEA;}
}

接口内的成员变量是final static public的,把枚举类放在接口类纯粹简洁,方便管理。
使用方法:

1
Food food = Food.Appetizer.SALAD;

上面存在的一个限制是,如果我要遍历所有的枚举类(比如我要浏览一个菜单中所有的菜品),会显得不太方便,解决方案是使用组合:如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public enum Meal2 {
APPETIZER(Food.Appetizer.class), MAINCOURSE(Food.MainCourse.class), DESSERT(Food.Dessert.class), COFFEE(Food.Coffee.class);
private Food[] values;

private Meal2(Class<? extends Food> kind) {
values = kind.getEnumConstants();
}

public interface Food {
enum Appetizer implements Food {SALAD, SOUP, SPRING_ROLLS;}

enum MainCourse implements Food {LASAGNE, BURRITO, PAD_THAI, LENTILS, HUMMOUS, VINDALOO;}

enum Dessert implements Food {TIRAMISU, GELATO, BLACK_FOREST_CAKE, FRUIT, CREME_CARAMEL;}

enum Coffee implements Food {BLACK_COFFEE, DECAF_COFFEE, ESPRESSO, LATTE, CAPPUCCINO, TEA, HERB_TEA;}
}

public Food randomSelection() {
return Enums.random(values);
}

public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
for (Meal2 meal : Meal2.values()) {
Food food = meal.randomSelection();
System.out.println(food);
}
System.out.println("---");
}
}
}

上面类似把枚举放到了另一个枚举类中,遍历起来会带来很大的方便。
关于上面两种用法,都是对于无法继承的一个补偿,但是总的来说,使用枚举的初衷就在于简单,所以尽量不要考虑太多复杂的代码。

适应枚举的容器

枚举类还有个不足就是一旦定义之后,就无法增加或者删除,那么如果有时候需要对一个固定的枚举类做一个筛选,就需要配合容器来使用,但是枚举类相比其他的类型天生就有一个“去重”的优势,考虑到这个优势,就可以设计使用枚举类的更高效的容器:

如何设计更高效的Set容器?

EnumSet:底层使用位运算实现,速度不要太快。用来操作枚举类
把所有的枚举类都放到Set中作为所有的元素,保证不重复。
用Long的位数0,1来决定某个枚举实例存在性。

使用方法:

1
2
3
4
5
6
7
8
//创建一个包含指定元素类型的所有元素的枚举 set
EnumSet<SexTwo> setAll = EnumSet.allOf(SexTwo.class);
//创建一个指定范围的Set
EnumSet<SexTwo> setRange = EnumSet.range(SexTwo.MALE,SexTwo.FEMALE);
//创建一个指定枚举类型的空set
EnumSet<SexTwo> setEmpty = EnumSet.noneOf(SexTwo.class);
//复制一个set
EnumSet<SexTwo> setNew = EnumSet.copyOf(setRange);

虽然Long只有64位,限制了枚举类中实例的个数,但是Long[]就不只64位了,这也是EnumSet有两个实现的原因。

如何设计更高效的Map容器?

EnumMap:底层使用数组作为实现,速度也不要太快。
让枚举类作为key,确保了key的不重复。
枚举类的ordinal()作为数组的索引。

枚举类的应用

  1. 用作责任链

什么是责任链模式?

在面向对象的设计中,责任链模式是由命令对象源和一系列处理对象组成的设计模式。每个处理对象都包含定义它可以处理的命令对象类型的逻辑;其余的传递给链中的下一个处理对象。(维基百科)
特性:
①一个请求传递给很多个处理对象,不是指向性的,自动寻找可以被解决的处理对象。
②能够处理请求的对象称为责任链,是可以动态生成的。

生成责任链有很多种方式,下面给出一种使用链表的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

public enum RequestType {
DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX
}

public class Request {

private final RequestType requestType;
private final String requestDescription;
private boolean handled;

public Request(final RequestType requestType, final String requestDescription) {
this.requestType = Objects.requireNonNull(requestType);
this.requestDescription = Objects.requireNonNull(requestDescription);
}

public String getRequestDescription() { return requestDescription; }

public RequestType getRequestType() { return requestType; }

public void markHandled() { this.handled = true; }

public boolean isHandled() { return this.handled; }

@Override
public String toString() { return getRequestDescription(); }
}

public abstract class RequestHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class);
private RequestHandler next;

public RequestHandler(RequestHandler next) {
this.next = next;
}

public void handleRequest(Request req) {
if (next != null) {
next.handleRequest(req);
}
}

protected void printHandling(Request req) {
LOGGER.info("{} handling request \"{}\"", this, req);
}

@Override
public abstract String toString();
}

public class OrcCommander extends RequestHandler {
public OrcCommander(RequestHandler handler) {
super(handler);
}

@Override
public void handleRequest(Request req) {
if (req.getRequestType().equals(RequestType.DEFEND_CASTLE)) {
printHandling(req);
req.markHandled();
} else {
super.handleRequest(req);
}
}

@Override
public String toString() {
return "Orc commander";
}
}

// OrcOfficer and OrcSoldier are defined similarly as OrcCommander

public class OrcKing {
RequestHandler chain;

public OrcKing() {
buildChain();
}

private void buildChain() {
chain = new OrcCommander(new OrcOfficer(new OrcSoldier(null)));
}

public void makeRequest(Request req) {
chain.handleRequest(req);
}

static void main(){
OrcKing king = new OrcKing();
king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle")); // Orc commander handling request "defend castle"
king.makeRequest(new Request(RequestType.TORTURE_PRISONER, "torture prisoner")); // Orc officer handling request "torture prisoner"
king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax")); // Orc soldier handling request "collect tax"
}
}

使用枚举类也可以构建责任链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
class Mail {   // The NO’s lower the probability of random selection:   
enum GeneralDelivery {YES, NO1, NO2, NO3, NO4, NO5}

enum Scannability {UNSCANNABLE, YES1, YES2, YES3, YES4}

enum Readability {ILLEGIBLE, YES1, YES2, YES3, YES4}

enum Address {INCORRECT, OK1, OK2, OK3, OK4, OK5, OK6}

enum ReturnAddress {MISSING, OK1, OK2, OK3, OK4, OK5}

GeneralDelivery generalDelivery;
Scannability scannability;
Readability readability;
Address address;
ReturnAddress returnAddress;
static long counter = 0;
long id = counter++;

public String toString() {
return "Mail " + id;
}

public String details() {
return toString() + ", General Delivery: " + generalDelivery + ", Address Scanability: " + scannability + ", Address Readability: " + readability + ", Address Address: " + address + ", Return address: " + returnAddress;
} // Generate test Mail:

public static Mail randomMail() {
Mail m = new Mail();
m.generalDelivery = Enums.random(GeneralDelivery.class);
m.scannability = Enums.random(Scannability.class);
m.readability = Enums.random(Readability.class);
m.address = Enums.random(Address.class);
m.returnAddress = Enums.random(ReturnAddress.class);
return m;
}

public static Iterable<Mail> generator(final int count) {
return new Iterable<Mail>() {
int n = count;

public Iterator<Mail> iterator() {
return new Iterator<Mail>() {
public boolean hasNext() {
return n-- > 0;
}

public Mail next() {
return randomMail();
}

public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
}
};
}
}

public class PostOffice {
enum MailHandler {
GENERAL_DELIVERY {
boolean handle(Mail m) {
switch (m.generalDelivery) {
case YES:
print("Using general delivery for " + m);
return true;
default:
return false;
}
}
}, MACHINE_SCAN {
boolean handle(Mail m) {
switch (m.scannability) {
case UNSCANNABLE:
return false;
default:
switch (m.address) {
case INCORRECT:
return false;
default:
print("Delivering " + m + " automatically");
return true;
}
}
}
}, VISUAL_INSPECTION {
boolean handle(Mail m) {
switch (m.readability) {
case ILLEGIBLE:
return false;
default:
switch (m.address) {
case INCORRECT:
return false;
default:
print("Delivering " + m + " normally");
return true;
}
}
}
}, RETURN_TO_SENDER {
boolean handle(Mail m) {
switch (m.returnAddress) {
case MISSING:
return false;
default:
print("Returning " + m + " to sender");
return true;
}
}
};

abstract boolean handle(Mail m);
}

static void handle(Mail m) {
for (MailHandler handler : MailHandler.values()) if (handler.handle(m)) return;
print(m + " is a dead letter");
}

public static void main(String[] args) {
for (Mail mail : Mail.generator(10)) {
print(mail.details());
handle(mail);
print("*****");
}
}
}

多路分发

什么是分发?

根据对象的类型而对方法进行的选择,就是分发(Dispatch)。

什么是静态分发和动态分发?

静态分发:编译时期根据对象的类型选择方法
动态分发:运行时期根据对象的实际类型“置换”正确的方法

什么是单路分发和多路分发?

单路分发:可以根据一个宗量的类型进行对方法的选择
多路分发:可以根据多于一个的宗量的类型对方法进行选择

JAVA所属的分发类别?

编译时期,可以根据参数的类型或者根据返回值得类型进行方法的选择,属于静态多路分发。
运行时期,可以根据返回值的实际类型进行方法的置换,无法根据参数的实际类型进行方法的置换,属于动态单路分发。

如何简洁的实现动态多路分发?

使用多次单路分发(一次动态:多态,一次静态:重载)来实现动态多路分发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
interface Item {   
Outcome compete(Item it);
Outcome eval(Paper p);
Outcome eval(Scissors s);
Outcome eval(Rock r);
}

class Paper implements Item {
public Outcome compete(Item it) {return it.eval(this);}
public Outcome eval(Paper p) {return DRAW;}
public Outcome eval(Scissors s) {return WIN;}
public Outcome eval(Rock r) {return LOSE;}
public String toString() {return "Paper";}
}

class Scissors implements Item {
public Outcome compete(Item it) {return it.eval(this);}
public Outcome eval(Paper p) {return LOSE;}
public Outcome eval(Scissors s) {return DRAW;}
public Outcome eval(Rock r) {return WIN;}
public String toString() {return "Scissors";}
}

class Rock implements Item {
public Outcome compete(Item it) {return it.eval(this);}
public Outcome eval(Paper p) {return WIN;}
public Outcome eval(Scissors s) {return LOSE;}
public Outcome eval(Rock r) {return DRAW;}
public String toString() {return "Rock";}
}

public class RoShamBo1 {
static final int SIZE = 20;
private static Random rand = new Random(47);

public static Item newItem() {
switch (rand.nextInt(3)) {
default:
case 0:
return new Scissors();
case 1:
return new Paper();
case 2:
return new Rock();
}
}

public static void match(Item a, Item b) {
System.out.println(a + " vs. " + b + ": " + a.compete(b));
}

public static void main(String[] args) {
for (int i = 0; i < SIZE; i++) match(newItem(), newItem());
}
}

多路分发的作用?

在于可让调用方法的语法更加的优雅,效果就是不需要关注参数的具体类型,教给程序去判断。

当把上面的多路分发的方法用于枚举类类型的时候,会有问题。原因在于枚举类无法实现重载,怎么办?

自然有适用于枚举类的多路分发的方法,案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public interface Competitor<T extends Competitor<T>> {   Outcome compete(T competitor); }

public class RoShamBo {
public static <T extends Competitor<T>> void match(T a, T b) {
System.out.println( a + " vs. " + b + ": " + a.compete(b));
}
public static <T extends Enum<T> & Competitor<T>> void play(Class<T> rsbClass, int size) {
for(int i = 0; i < size; i++)
match(Enums.random(rsbClass) , Enums.random(rsbClass));
}
}

public enum RoShamBo2 implements Competitor<RoShamBo2> {
PAPER(DRAW, LOSE, WIN), SCISSORS(WIN, DRAW, LOSE), ROCK(LOSE, WIN, DRAW);
private Outcome vPAPER, vSCISSORS, vROCK;

RoShamBo2(Outcome paper, Outcome scissors, Outcome rock) {
this.vPAPER = paper;
this.vSCISSORS = scissors;
this.vROCK = rock;
}

public Outcome compete(RoShamBo2 it) {
switch (it) {
default:
case PAPER:
return vPAPER;
case SCISSORS:
return vSCISSORS;
case ROCK:
return vROCK;
}
}

public static void main(String[] args) {
RoShamBo.play(RoShamBo2.class, 20);
}
}

于之前的相比,区别在于第一次使用了枚举类的方法调用,第二次使用了枚举类的switch()。

可以使用EnumMap进一步优化:

1
table = new EnumMap<RoShamBo5,EnumMap<RoShamBo5,Outcome>>(RoShamBo5.class);

因为对于枚举类的多路分发而言,没有复杂的逻辑的情况下,保存两个不同参数下的结果(写死)即可。
既然结果已经写死了,那么可以进一步使用数组优化:

1
2
3
4
5
private static Outcome[][] table = {     
{ DRAW, LOSE, WIN }, // PAPER
{ WIN, DRAW, LOSE }, // SCISSORS
{ LOSE, WIN, DRAW }, // ROCK
};

数组的维度可以使用ordinal()来取得。

本文标题:19 枚举

文章作者:Sun

发布时间:2018年11月26日 - 11:11

最后更新:2018年12月12日 - 16:12

原始链接:https://sunyi720.github.io/2018/11/26/THING IN JAVA/19 枚举/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。